/*
 * $AIST_Release: 0.9.0 $
 * Copyright 2011 Information Technology Research Institute, National
 * Institute of Advanced Industrial Science and Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package generator;

import java.lang.RuntimeException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.util.Calendar;
import java.util.Map;
import java.util.Locale;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.net.URL;
import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.lang.Double;
import java.lang.Long;
import java.util.StringTokenizer;
import java.util.ArrayList;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.ByteString;

/**
 * Build-in functions.
 */
public class BuildIn {

    static public Helpers.ByteStringWrapper func_bytes(String src) {
        return new Helpers.ByteStringWrapper(ByteString.copyFromUtf8(src));
    }

    static public Helpers.ByteStringWrapper func_bytes(Long src) {
        try {
          ByteString.Output bs = ByteString.newOutput(CodedOutputStream.computeInt64SizeNoTag(src.longValue()));
          CodedOutputStream cos = CodedOutputStream.newInstance(bs);

          cos.writeInt64NoTag(src.longValue());
          cos.flush();

          return new Helpers.ByteStringWrapper(bs.toByteString());
        } catch (IOException e) {
            // TODO: Log
            return null;
        }
    }

    static public Helpers.ByteStringWrapper func_bytes(Double src) {
        try {
          ByteString.Output bs = ByteString.newOutput(CodedOutputStream.computeDoubleSizeNoTag(src.doubleValue()));
          CodedOutputStream cos = CodedOutputStream.newInstance(bs);

          cos.writeDoubleNoTag(src.longValue());
          cos.flush();

          return new Helpers.ByteStringWrapper(bs.toByteString());
        } catch (IOException e) {
            // TODO: Log
            return null;
        }
    }

    // BuildIn functions
      
    public static <T> Long func_def(T o) {
        return bool2long(o != null);
    } 

    static public Double func_float(Helpers.ByteStringWrapper src) {
        try {
            return src.get().newCodedInput().readDouble();
        } catch (IOException e) {
            // TODO: Log
            return null;
        }
    }

    static public Double func_float(Long src) throws IOException {
        return src.doubleValue();
    }

    static public String func_string(Helpers.ByteStringWrapper src) {
        return func_string(src, "UTF-8");
    }

    static public String func_string(Helpers.ByteStringWrapper src, String enc) {
        try {
            return src.get().toString(enc);
        } catch (UnsupportedEncodingException e) {
            // TODO: Log
            return null;
        }
    }

    static public String func_string(Helpers.Time src) {
        return new Date(src.value/1000).toString();
    }

    static public String func_string(Double src) {
        return src.toString();
    }
    
    static public Long func_int(Helpers.ByteStringWrapper src) {
        try {
            return src.get().newCodedInput().readInt64();
        } catch (IOException e) {
            /* TODO: log */
            return null;
        }
    }

    static public Long func_int(Double src) {
        return src.longValue();
    }

    static public Long func_int(Helpers.Time src) {
        return src.value;
    }

    /*
     *  TODO: Deletes this function when becoming unnecessary. 
     */ 
    static public List<String> func_ssplit(String src) {
        return Arrays.asList(src.split("\\s+"));
    }

    static public List<String> func_split(String src) {
        ArrayList<String> r = new ArrayList<String>();
        StringTokenizer st = new StringTokenizer(src);

        while (st.hasMoreTokens()) {
            r.add(st.nextToken());
        }

        return r;
    }

    static public <T> Long func_debug(T o) {
        System.err.println(o.toString());
        return 1l;
    }

    static private Calendar cal = Calendar.getInstance(); 
    static private Long ofDate(int kind, Helpers.Time d) {
      try {
        synchronized(cal) {
          cal.setTimeInMillis(d.value / 1000); // Convert microsec to millisec.
          return (long) cal.get(kind);
        }
      } catch (RuntimeException e) {
        throw new RuntimeException("value = " + d.value/1000, e);
      }
    }

    static public Long func_yearof(Helpers.Time t)           { return ofDate(Calendar.YEAR,                 t); }
    static public Long func_monthof(Helpers.Time t)          { return ofDate(Calendar.MONTH,                t); }
    static public Long func_weekofyear(Helpers.Time t)       { return ofDate(Calendar.WEEK_OF_YEAR,         t); }
    static public Long func_weekofmonth(Helpers.Time t)      { return ofDate(Calendar.WEEK_OF_MONTH,        t); }
    static public Long func_dayofmonth(Helpers.Time t)       { return ofDate(Calendar.DAY_OF_MONTH,         t); }
    static public Long func_dayofyear(Helpers.Time t)        { return ofDate(Calendar.DAY_OF_YEAR,          t); }
    static public Long func_dayofweek(Helpers.Time t)        { return ofDate(Calendar.DAY_OF_WEEK,          t); }
    static public Long func_dayofweekinmonth(Helpers.Time t) { return ofDate(Calendar.DAY_OF_WEEK_IN_MONTH, t); }
    static public Long func_hourof(Helpers.Time t)           { return ofDate(Calendar.HOUR_OF_DAY,          t); }
    static public Long func_minuteof(Helpers.Time t)         { return ofDate(Calendar.MINUTE,               t); }
    static public Long func_secondof(Helpers.Time t)         { return ofDate(Calendar.SECOND,               t); }
    static public Long func_millisecondof(Helpers.Time t)    { return ofDate(Calendar.MILLISECOND,          t); }

    static public Helpers.Time func_tranctohour(Helpers.Time t) { 
        cal.setTimeInMillis(t.value / 1000); // Convert microsec to millisec.
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);

        return new Helpers.Time(cal.getTimeInMillis() * 1000);
    }

    static private SimpleDateFormat dateFormat = new SimpleDateFormat("MMM d HH:mm:ss z yyyy", Locale.US);
    static public Helpers.Time func_time(String s) {
        try {
          Date d = dateFormat.parse(s);
          return new Helpers.Time(d.getTime() * 1000);
        } catch (ParseException e) {
          // TODO: Log
          return null;
        }
    }

    static public Helpers.Time func_time(Long src) { 
        return new Helpers.Time(src.longValue());
    }

    static public String func_string(Long src) { 
        return src.toString();
    }

    static public Long func_len(String src) { 
        return (long) src.length();
    }

    static public Long func_len(Helpers.ByteStringWrapper src) { 
        return (long) src.get().size();
    }

    static public <K, V> Long func_len(Map<K, V> src) {
        return (long) src.size();
    }

    static public <T> Long func_len(List<T> src) {
        return (long) src.size();
    }

    static public String func_domain(String s) {
        try {
            return new URL(s).getHost();
        } catch (MalformedURLException e) {
            // TODO: Log
            return null;
        }
    }

    static public Long func_assert(Long cond) {
        assert(cond != 0l);

        return 0L;
    }

    static public Long func_test(Long cond) {
        if (cond == 0) {
            throw new RuntimeException("Sawzall Clone: test failed");
        }

        return 0L;
    }

    static public Long func_new_int(Long v) {
        return new Long(v);
    }

    /**
     * Converts boolean value to long value.
     */
    private static Long bool2long(Boolean b) {
        return b.booleanValue() ? 1l : 0l;
    }
}
